package load.parent;

import core.*;
import java.io.*;
import java.net.*;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Map;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import view.GUI.ConnectFileFilter;
import view.GUI.LabelReader;

/**
 *
 * @author Ben
 */
public class Wrapper extends StdNode{

    public static class Signature  implements Serializable{
        private String name;
        private Class[] params;
        private Class returnType;

        public Signature(String name, Class[] params, Class returnType){
            this.name = name;
            this.params = params;
            this.returnType = returnType;
        }

        public String getName() {
            return name;
        }

        public Class[] getParams() {
            return params;
        }

        public Class getReturnType() {
            return returnType;
        }

    }

    private Map<Port, Signature> portToSignature = new HashMap<Port, Signature>();
    //the following 2 fields are needed for the getWrappedClass() method
    private String targetClassName;
    private String classPath;
    private DispatcherPort resChanged = new DispatcherPort();

    protected TriggerPort argChanged = new TriggerPort() {
            @Override
            public void callInternal() {
                    resChanged.callTargets();
            }
    };

    public String getTargetClassName() {
        try{
            return getWrappedClass().getSimpleName();
        }catch(Exception ex){
            return null;
        }
    }

    private File openClassChooser(){
        JFileChooser fileChooser = new JFileChooser(".");
        fileChooser.setFileFilter(new ConnectFileFilter(".class (java class)", new String[]{"class", "java class"}));
        fileChooser.setDialogTitle(LabelReader.getInstance().getString("BrowseClass"));
        int returnVal = fileChooser.showOpenDialog(null);
        if (returnVal == JFileChooser.APPROVE_OPTION) {
            return fileChooser.getSelectedFile();
        }else return null;
    }
/**
 *
 * @param className
 * @return true if className is the name of standard java class
 */
    private boolean isJavaJREClass(String className){
        return className.startsWith("java.") || className.startsWith("javax.");
    }

    public Wrapper(){
       this(false); 
    }

    public Wrapper(boolean onlyStatic){
        super();
        //ask the class name
        this.targetClassName = JOptionPane.showInputDialog(
                            LabelReader.getInstance().getString("InputClassToWrapp"),"");
        if(!isJavaJREClass(targetClassName)){
            File classPathFile = openClassChooser();
            if(targetClassName != null && classPathFile != null){
                  File classPathShort = new File(classPathFile.getPath().substring(0,
                      classPathFile.getPath().length() - targetClassName.length() - 6));//.class = 6 characters
                  this.classPath=classPathShort.getPath();
            }
        }
        wrappClass(onlyStatic);
    }

    public Wrapper(Class c, boolean onlyStatic){
        super();
        this.targetClassName=c.getName();
        if(!isJavaJREClass(targetClassName)){
            this.classPath= c.getProtectionDomain().getCodeSource().getLocation().toString();
            this.classPath= this.classPath.replace("/", "\\").substring(6);
            java.lang.System.out.println(this.classPath);
        }
        wrappClass(onlyStatic);
    }

    public Wrapper(Class c){
        this(c,false);
    }

    public static Class classMapper(Class c){
        Class result = c;
        if(c == int.class){
            result = Integer.class;
        }else if(c == double.class){
            result = Double.class;
        }else if(c == boolean.class){
            result = Boolean.class;
        }
        return result;
    }

/* Changing the classpath was found on the following webpage:
 * http://www.java-tips.org/java-se-tips/java.lang/how-to-load-a-class-that-is-not-on-the-classpath.html
 */    
    protected Class getWrappedClass() throws Exception{
        try{
            if(classPath != null){//custom Java class
                URL url = (new File(classPath)).toURI().toURL();
                URL[] urls = new URL[]{url};
                ClassLoader loader = new URLClassLoader(urls);
                return loader.loadClass(targetClassName);
            }else{//standard Java class
                return Class.forName(targetClassName);
            }
        }catch(Error er){//This is the first time I've wrote an error catch
             JOptionPane.showMessageDialog(null,"Wrapper: " + er.getMessage(),
                      LabelReader.getInstance().getString("Error"),
                      JOptionPane.ERROR_MESSAGE);
            return null;
        }
    }

    protected Object getWrappedObject(){
        try{
            return getWrappedClass().newInstance();
        }catch(Exception e){
            return null;//needed for static methods with no default constructor available
        }
    }

    private Port addOutPort(Method m){
        OutPort res = new OutPort(classMapper(m.getReturnType()), resChanged){
            public Object getValueInternal() {
               return invokeNoParams(this);
            }
        };
        return res;
    }

    private Port addTrigger(Method m){
        TriggerPort triggerPort = new TriggerPort() {
            protected void callInternal() {
                invokeNoParams(this);
            }
        };
        return triggerPort;
    }

    public Object invoke(Signature sig, Object[] args) throws Exception{
        Class c = getWrappedClass();
        if(sig.getName().equals(c.getName())){
            Constructor ctr = c.getConstructor(sig.getParams());
            return ctr.newInstance(args);
        }else{
            Method m = c.getMethod(sig.getName(),sig.getParams());
            return m.invoke(getWrappedObject(),args);
        }
    }

    private Object invokeNoParams(Port p){
        try{
            Object obj = getWrappedObject();
            Signature sig = portToSignature.get(p);
            Method m = getWrappedClass().getMethod(sig.getName(),sig.getParams());
            return m.invoke(obj);
        }catch(Exception ex){
           JOptionPane.showMessageDialog(null,"Wrapper: " +  ex.getMessage(),
              LabelReader.getInstance().getString("Error"), JOptionPane.ERROR_MESSAGE);
           return null;
        }
    }

    protected Port addFunctionNode(final Function func){
        OutPort res = new OutPort(Function.class, resChanged){
            public Object getValueInternal() {
               return func;
            }
        };
        return res;
    }

    //returns a unique name based on the given name (needed for overloaded methods)
    protected String uniqueNameFrom(String name){
        int i = 1;
        String uniqueName = name;
        while(this.getPort(uniqueName) != null){
            uniqueName = name + "_" + i;
            i++;
        }
        return uniqueName;
    }

    protected Port addOutPort(java.lang.reflect.Constructor ctr) throws Exception{
        OutPort res = new OutPort(classMapper(ctr.getDeclaringClass()), resChanged){
            public Object getValueInternal() {
               try{ 
                  return getWrappedClass().newInstance();
               }catch(Exception ex){
                  JOptionPane.showMessageDialog(null,"Wrapper: " +  ex.getMessage(),
                          LabelReader.getInstance().getString("Error"),
                          JOptionPane.ERROR_MESSAGE);                
                  return null; 
               }
            }
        };
        return res;
    }



    private void wrappClass(boolean onlyStatic){
        try{
 	    for (Method m: getWrappedClass().getMethods()){
                if((onlyStatic && Modifier.isStatic(m.getModifiers()))
                   || (!onlyStatic && !Modifier.isStatic(m.getModifiers()))){
                    Port p = null;
                    if(m.getParameterTypes().length == 0){
                        if(m.getReturnType().equals(void.class)){
                             p = addTrigger(m);
                        }else{
                             p = addOutPort(m);
                        }
                        Signature sig = new Signature(m.getName(),m.getParameterTypes(),
                                                      m.getReturnType());
                        portToSignature.put(p, sig);
                        this.putPort("resChanged", resChanged);
                    }else{
                        p = addFunctionNode(new Function(m,this));
                    }
                    this.putPort(uniqueNameFrom(m.getName()), p);
                }
            }
        }catch(Exception ex){
            JOptionPane.showMessageDialog(null,"Wrapper: " +  ex.getMessage(),
                      LabelReader.getInstance().getString("Error"),
                      JOptionPane.ERROR_MESSAGE);
        }
    }

}
